// cpp/imageCache/napi_init.cpp

#include "ImageCache/ImageFileCache.h"
#include "napi/native_api.h"
#include <arkui/native_interface.h>
#include <arkui/native_node_napi.h>
#include <hilog/log.h>
#include <ace/xcomponent/native_interface_xcomponent.h>
#include <unordered_map>
#include <string>

napi_value CreateContainerNode(napi_env env, napi_callback_info info);
napi_value AttachImageNode(napi_env env, napi_callback_info info);
napi_value InitImageFileCache(napi_env env, napi_callback_info info);
napi_value Preload(napi_env env, napi_callback_info info);
napi_value CancelPreload(napi_env env, napi_callback_info info);
napi_value GetImage(napi_env env, napi_callback_info info);
napi_value ClearCache(napi_env env, napi_callback_info info);
void SetNativeXComponent(const std::string &id, OH_NativeXComponent *nativeXComponent);
static std::unordered_map<std::string, OH_NativeXComponent *> nativeXComponentMap_;
static std::unordered_map<std::string, ArkUI_NodeHandle> nativeNodeMap_;
static ArkUI_NativeNodeAPI_1 *nodeAPI = reinterpret_cast<ArkUI_NativeNodeAPI_1 *>(
    OH_ArkUI_QueryModuleInterfaceByName(ARKUI_NATIVE_NODE, "ArkUI_NativeNodeAPI_1"));
static ImageFileCache *imageFileCache = ImageFileCache::GetInstance();
void Export(napi_env env, napi_value exports);


EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports) {
    OH_LOG_Print(LOG_APP, LOG_INFO, 0xff00, "init", "NAPI init begin");
    if (env == nullptr || exports == nullptr) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, 0xff00, "init", "NAPI init failed, %{public}p, %{public}p", env, exports);
    }
    napi_property_descriptor desc[] = {
        {"createContainerNode", nullptr, CreateContainerNode, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"attachImageNode", nullptr, AttachImageNode, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"initImageFileCache", nullptr, InitImageFileCache, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"preload", nullptr, Preload, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"cancelPreload", nullptr, CancelPreload, nullptr, nullptr, nullptr, napi_default, nullptr},
        // { "getImage", nullptr, GetImage, nullptr, nullptr, nullptr, napi_default, nullptr },
        {"clearCache", nullptr, ClearCache, nullptr, nullptr, nullptr, napi_default, nullptr}};
    napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
    Export(env, exports);
    return exports;
}
EXTERN_C_END

static napi_module demoModule = {
    .nm_version = 1,
    .nm_flags = 0,
    .nm_filename = nullptr,
    .nm_register_func = Init,
    .nm_modname = "imageCache",
    .nm_priv = ((void *)0),
    .reserved = {0},
};

extern "C" __attribute__((constructor)) void RegisterEntryModule(void) { napi_module_register(&demoModule); }

const int LOG_PRINT_DOMAIN = 0xff00;
#define CHECK_NULL_RETURN(env, info)                                                                                   \
    {                                                                                                                  \
        if (env == nullptr || info == nullptr) {                                                                       \
            OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Init", "ERROR env %{public}p or info %{public}p}",     \
                         env, info);                                                                                   \
            return nullptr;                                                                                            \
        }                                                                                                              \
    }

#define CHECK_VALID_ARGS_RETURN(env, info, argCnt, args)                                                               \
    {                                                                                                                  \
        if (napi_get_cb_info(env, info, &argCnt, args, nullptr, nullptr) != napi_ok) {                                 \
            OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Init", "Failed to get argument.");                     \
            return nullptr;                                                                                            \
        }                                                                                                              \
    }

#define CHECK_VALID_TYPES_RETURN(env, arg, checktype)                                                                  \
    {                                                                                                                  \
        napi_valuetype valuetype;                                                                                      \
        if (napi_typeof(env, args[0], &valuetype) != napi_ok) {                                                        \
            OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Init", "napi typeof failed");                          \
            napi_throw_type_error(env, NULL, "napi typeof failed");                                                    \
            return nullptr;                                                                                            \
        }                                                                                                              \
        if (valuetype != checktype) {                                                                                  \
            OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Init", "napi type check failed");                      \
            napi_throw_type_error(env, NULL, "napi type check failed");                                                \
            return nullptr;                                                                                            \
        }                                                                                                              \
    }

void Export(napi_env env, napi_value exports) {
    if (env == nullptr || exports == nullptr) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Init", "ERROR env %{public}p or exports %{public}p}", env,
                     exports);
        return;
    }

    napi_value exportInstance = nullptr;
    if (napi_get_named_property(env, exports, OH_NATIVE_XCOMPONENT_OBJ, &exportInstance) != napi_ok) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Init", "Export fail napi_get_named_property");
        return;
    }

    OH_NativeXComponent *nativeXComponent = nullptr;
    if (napi_unwrap(env, exportInstance, reinterpret_cast<void **>(&nativeXComponent)) != napi_ok) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Init", "Export fail napi_unwrap");
        return;
    }

    char idStr[OH_XCOMPONENT_ID_LEN_MAX + 1] = {'\0'};
    uint64_t idSize = OH_XCOMPONENT_ID_LEN_MAX + 1;
    if (OH_NativeXComponent_GetXComponentId(nativeXComponent, idStr, &idSize) != OH_NATIVEXCOMPONENT_RESULT_SUCCESS) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Init", "Export fail OH_NativeXComponent_GetXComponentId");
        return;
    }
    OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "Init", "xComponent string: %{public}s", idStr);
    if (nativeXComponent != nullptr) {
        SetNativeXComponent(idStr, nativeXComponent);
    }
}

OH_NativeXComponent *GetNativeXComponent(const std::string &id) { return nativeXComponentMap_[id]; }

ArkUI_NodeHandle GetNativeNode(const std::string &id) { return nativeNodeMap_[id]; }
void SetNativeNode(const std::string &id, const ArkUI_NodeHandle node) {
    if (node == nullptr) {
        nativeNodeMap_[id] = node;
        return;
    }
    if (nativeNodeMap_.find(id) == nativeNodeMap_.end()) {
        nativeNodeMap_[id] = node;
        return;
    }
    if (nativeNodeMap_[id] != node) {
        nativeNodeMap_[id] = node;
    }
}
void SetNativeXComponent(const std::string &id, OH_NativeXComponent *nativeXComponent) {
    if (nativeXComponent == nullptr) {
    }
    if (nativeXComponentMap_.find(id) == nativeXComponentMap_.end()) {
        nativeXComponentMap_[id] = nativeXComponent;
    }
    if (nativeXComponentMap_[id] != nativeXComponent) {
        // do nothing;
    }
}

napi_value CreateContainerNode(napi_env env, napi_callback_info info) {
    OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "Init", "CreateContainerNode begin");
    CHECK_NULL_RETURN(env, info);
    size_t argCnt = 1;
    napi_value args[10] = {nullptr};
    CHECK_VALID_ARGS_RETURN(env, info, argCnt, args);
    if (argCnt != 1) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Init",
                     "CreateContainerNode Wrong number of argument: %{public}lu", argCnt);
        napi_throw_type_error(env, NULL, "Wrong number of argument");
        return nullptr;
    }
    CHECK_VALID_TYPES_RETURN(env, args[0], napi_string);
    char idStr[OH_XCOMPONENT_ID_LEN_MAX + 1];
    const uint64_t idSize = OH_XCOMPONENT_ID_LEN_MAX + 1;
    size_t length;
    if (napi_get_value_string_utf8(env, args[0], idStr, idSize, &length) != napi_ok) {
        napi_throw_type_error(env, NULL, "napi_get_value_string_utf8 failed");
        return nullptr;
    }
    idStr[length] = '\0';
    OH_NativeXComponent *component = GetNativeXComponent(idStr);
    if (component == nullptr) {
        return nullptr;
    }
    auto scroll = nodeAPI->createNode(ARKUI_NODE_SCROLL);
    auto column = nodeAPI->createNode(ARKUI_NODE_COLUMN);
    auto row = nodeAPI->createNode(ARKUI_NODE_ROW);
    // ArkUI_NumberValue value[1] = {{.f32 = 1000}};
    // ArkUI_AttributeItem item0 = {value, 1};
    nodeAPI->addChild(scroll, column);
    SetNativeNode("column", column);
    OH_NativeXComponent_AttachNativeRootNode(component, scroll);
    return nullptr;
}

napi_value AttachImageNode(napi_env env, napi_callback_info info) {
    OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "Init", "AttachImageNode begin");
    CHECK_NULL_RETURN(env, info);
    size_t argCnt = 2;
    napi_value args[10] = {nullptr};
    CHECK_VALID_ARGS_RETURN(env, info, argCnt, args);
    if (argCnt != 2) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Init", "Wrong number of argument: %{public}lu", argCnt);
        napi_throw_type_error(env, NULL, "Wrong number of argument");
        return nullptr;
    }
    CHECK_VALID_TYPES_RETURN(env, args[0], napi_string);
    char idStr[OH_XCOMPONENT_ID_LEN_MAX + 1];
    const uint64_t idSize = OH_XCOMPONENT_ID_LEN_MAX + 1;
    size_t length;
    if (napi_get_value_string_utf8(env, args[0], idStr, idSize, &length) != napi_ok) {
        napi_throw_type_error(env, NULL, "napi_get_value_string_utf8 failed");
        return nullptr;
    }

    int32_t type = 0; // 0: image;
    napi_get_value_string_utf8(env, args[1], nullptr, 0, &length);
    char *buffer = new char[length + 1];
    napi_get_value_string_utf8(env, args[1], buffer, length + 1, &length);
    buffer[length] = '\0';

    if (type == 0) {
        auto column = GetNativeNode("column");
        auto image = nodeAPI->createNode(ARKUI_NODE_IMAGE);
        auto path = imageFileCache->GetImageCache(buffer, image);
        if (path.length() > 0) {
            ArkUI_AttributeItem item0 = {.string = path.c_str()};
            nodeAPI->setAttribute(image, NODE_IMAGE_SRC, &item0);
        }
        nodeAPI->addChild(column, image);
    }
    return nullptr;
}
napi_value InitImageFileCache(napi_env env, napi_callback_info info) {
    CHECK_NULL_RETURN(env, info);
    size_t argc = 2;
    napi_value args[10] = {nullptr};
    CHECK_VALID_ARGS_RETURN(env, info, argc, args);
    if (argc != 2) {
        napi_throw_error(env, "-1001", "arg number incorrect");
        return nullptr;
    }

    CHECK_VALID_TYPES_RETURN(env, args[0], napi_string);
    size_t length = 0;
    if (napi_ok != napi_get_value_string_utf8(env, args[0], nullptr, 0, &length)) {
        napi_throw_error(env, "-1003", "napi get string value error");
        return nullptr;
    }

    char *buffer = new char[length + 1];
    if (napi_ok != napi_get_value_string_utf8(env, args[0], buffer, length + 1, &length)) {
        delete[] buffer;
        buffer = nullptr;
        napi_throw_error(env, "-1003", "napi get string value error");
        return nullptr;
    }
    buffer[length] = '\0';
    uint32_t intvalue;
    napi_get_value_uint32(env, args[1], &intvalue);
    imageFileCache->InitCache(buffer, intvalue, env, info);
    return nullptr;
}

napi_value Preload(napi_env env, napi_callback_info info) {
    CHECK_NULL_RETURN(env, info);
    size_t argc = 1;
    napi_value args[10] = {nullptr};
    CHECK_VALID_ARGS_RETURN(env, info, argc, args);
    if (argc != 1) {
        napi_throw_error(env, "-1001", "arg number incorrect");
        return nullptr;
    }
    napi_valuetype valueType;
    napi_typeof(env, args[0], &valueType);
    CHECK_VALID_TYPES_RETURN(env, args[0], napi_string);
    size_t length = 0;
    if (napi_ok != napi_get_value_string_utf8(env, args[0], nullptr, 0, &length)) {
        napi_throw_error(env, "-1003", "napi get string value error");
        return nullptr;
    }

    char *buffer = new char[length + 1];
    if (napi_ok != napi_get_value_string_utf8(env, args[0], buffer, length + 1, &length)) {
        delete[] buffer;
        buffer = nullptr;
        napi_throw_error(env, "-1003", "napi get string value error");
        return nullptr;
    }
    buffer[length] = '\0';

    imageFileCache->Preload(buffer);
    return nullptr;
}

napi_value CancelPreload(napi_env env, napi_callback_info info) {
    CHECK_NULL_RETURN(env, info);
    size_t argc = 1;
    napi_value args[10] = {0};
    CHECK_VALID_ARGS_RETURN(env, info, argc, args);
    if (argc != 1) {
        napi_throw_error(env, "-1001", "arg number incorrect");
        return nullptr;
    }
    napi_valuetype valueType;
    napi_typeof(env, args[0], &valueType);
    CHECK_VALID_TYPES_RETURN(env, args[0], napi_string);
    size_t length = 0;
    if (napi_ok != napi_get_value_string_utf8(env, args[0], nullptr, 0, &length)) {
        napi_throw_error(env, "-1003", "napi get string value error");
        return nullptr;
    }

    char *buffer = new char[length + 1];
    if (napi_ok != napi_get_value_string_utf8(env, args[0], buffer, length + 1, &length)) {
        delete[] buffer;
        buffer = nullptr;
        napi_throw_error(env, "-1003", "napi get string value error");
        return nullptr;
    }
    buffer[length] = '\0';
    imageFileCache->CancelRequest(buffer);
    return nullptr;
}

napi_value ClearCache(napi_env env, napi_callback_info ino) { return nullptr; }
